package com.sc.nft.service.impl;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sc.nft.config.sand.SDKConfig;
import com.sc.nft.config.sand.SandPayUtil;
import com.sc.nft.dao.OrderSettleDao;
import com.sc.nft.dao.OrderSplitFlowDao;
import com.sc.nft.dao.PayOrderDao;
import com.sc.nft.dao.UserOrderAccountFlowDao;
import com.sc.nft.dto.OrderSettleDTO;
import com.sc.nft.entity.*;
import com.sc.nft.enums.*;
import com.sc.nft.helper.LlPayHelper;
import com.sc.nft.mapper.ScOrderSettleMapper;
import com.sc.nft.pool.UserPool;
import com.sc.nft.service.LlUserExtService;
import com.sc.nft.service.ScOrderSettleService;
import com.sc.nft.service.UserInfoService;
import com.sc.nft.service.UserWalletService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static com.sc.nft.config.sand.SandPayUtil.SPLIT_CREATE_METHOD;
import static com.sc.nft.config.sand.SandPayUtil.SPLIT_CREATE_PATH;
import static com.sc.nft.helper.LlPayHelper.SUCCESS_CODE;

/**
 * <p>
 * 结算单 服务实现类
 * </p>
 *
 * @author PC
 * @since 2024-10-22
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class ScOrderSettleServiceImpl extends ServiceImpl<ScOrderSettleMapper, OrderSettle> implements ScOrderSettleService {
    private final OrderSettleDao orderSettleDao;
    private final UserOrderAccountFlowDao userOrderAccountFlowDao;
    private final OrderSplitFlowDao orderSplitFlowDao;
    private final Redisson redisson;
    private final LlPayHelper llPayHelper;
    private final UserWalletService userWalletService;
    private final UserInfoService userInfoService;
    private final LlUserExtService llUserExtService;
    private final PayOrderDao payOrderDao;


    /**
     * 生成结算单 增加用户待结算金额
     *
     * @param userId
     * @param payOrder
     * @param sellType
     * @param settleAmount
     * @param settleUserType
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderSettle create(Long orderId, BigDecimal buyPrice, String orderNo, Long userId, PayOrder payOrder,
                              WalletDetailTypeEnum sellType, BigDecimal settleAmount,
                              SettleUserTypeEnum settleUserType, ExpenditureRecordTypeEnum expenditureRecordTypeEnum,
                              BizTypeEnum bizTypeEnum, MarketLevelEnum marketLevelEnum) {
        //生成结算单
        OrderSettle orderSettle = orderSettleDao.addOrderSettle(orderId, buyPrice, orderNo, userId, payOrder, sellType,
                settleAmount, settleUserType, expenditureRecordTypeEnum, bizTypeEnum, marketLevelEnum);

        //增加待结算金额
        String key = StrUtil.format("{}:{}:{}", RedisKeyEnum.USER_ORDER_ACCOUNT_FLOW_LOCK.getKey(),
                orderSettle.getUserId(), orderSettle.getPayChannel().getCode());
        RLock lock = redisson.getLock(key);
        try {
            userOrderAccountFlowDao.increase(orderSettle.getUserId(), orderSettle.getPayChannel(),
                    orderSettle.getSettleAmount());
        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return orderSettle;
    }

    /**
     * 确认结算
     * TODO task发布需要暂停相关任务，出现过服务启停瞬间同毫秒执行3次的情况，未再次复现，可能是分布式锁颗粒度较细,
     * TODO 2024-11-5调整分布式锁范围颗粒度变大 需要注意后续情况
     *
     * @param orderSettle
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void settleConfirm(OrderSettle orderSettle) {
        //处理用户结算明细 用户结算明细存在竞争需要锁 根据用户和支付渠道锁定
        String key = StrUtil.format("{}:{}:{}", RedisKeyEnum.USER_ORDER_ACCOUNT_FLOW_LOCK.getKey(),
                orderSettle.getUserId(), orderSettle.getPayChannel().getCode());
        RLock lock = redisson.getLock(key);
        try {
            OrderSplitFlow orderSplitFlow = orderSplitFlowDao.queryByOrderSettle(orderSettle);
            if (orderSettle.getStatus().equals(SettleAccountStatusEnum.SETTLED) || ObjectUtil.isNotNull(orderSplitFlow)) {
                return;
            }
            //生成分账单 默认分账成功 避免因三方未返回明确状态而重复结算
            OrderSplitFlow splitFlow = orderSplitFlowDao.create(orderSettle);
            splitFlow.setSplitStatus(SplitStatusEnum.SUCCESS);
            splitFlow.setSplitType(SplitTypeEnum.SPLIT);
            //调用三方 金额大于0再去分账
            if (splitFlow.getSettleAmount().compareTo(BigDecimal.ZERO) > 0) {

                switch (orderSettle.getPayChannel()) {
                    case SAND:
                        if (orderSettle.getExpireTime().before(new Date())) {
                            //超时走系统钱包 如果是平台资金不做处理
                            if (!splitFlow.getUserId().equals(0L)) {
                                userWalletService.addSplitIncome(splitFlow.getUserId(), splitFlow.getOrderNo(),
                                        splitFlow.getSettleAmount(), orderSettle.getType(), orderSettle.getExpenditureRecordType(),
                                        "杉德分账订单超时走系统余额,分账单号：" + splitFlow.getSplitNo());
                                splitFlow.setSplitType(SplitTypeEnum.WALLET);
                            }
                        } else {
                            //走分账
                            String userMerchant = SDKConfig.getConfig().getMid();
                            if (splitFlow.getUserType().equals(SettleUserTypeEnum.USER)) {
                                //个人用户时需要查询用户云账户编码
                                UserInfo userInfo = userInfoService.getById(orderSettle.getUserId());
                                userMerchant = userInfo.getSandWithdrawUuid();
                            }
                            JSONObject header = SandPayUtil.getHeader(SPLIT_CREATE_METHOD, orderSettle.getPayMethod().getNewProductCode());
                            JSONObject body = SandPayUtil.getSplitCreateRequest(splitFlow, userMerchant);
                            JSONObject result = SandPayUtil.splitRequest(header, body, SPLIT_CREATE_PATH);
                            /**
                             * TODO 杉德分账只有撤销和完结通知  此处不存在撤销 资金也是完全分配完的 极端情况下出现未分尽的情形在分账冻结期最长60天后结算至主账户
                             * TODO 正常调用杉德分账申请只能通过主动查询获知状态 暂不做定时查询 待运营一段时间后再优化  2024-10-24 @Jozu
                             */
                            if (!StrUtil.equals(result.getJSONObject("head").getString("respCode"), "000000")) {
                                log.error("分账申请失败：" + result.getJSONObject("head").getString("respMsg"));
                                splitFlow.setSplitStatus(SplitStatusEnum.FAIL);
                            }
                            splitFlow.setReqParams(body.toJSONString());
                            splitFlow.setRespCode(result.getJSONObject("head").getString("respCode"));
                            splitFlow.setRespDesc(result.getJSONObject("head").getString("respMsg"));
                            splitFlow.setSplitType(SplitTypeEnum.SPLIT);

                        }
                        break;
                    case LL_PAY:
                        //需要支付人uuid
                        PayOrder payOrder = payOrderDao.getById(splitFlow.getPayOrderId());
                        UserInfo userInfo = userInfoService.getById(payOrder.getUserId());
                        //支付人连连认证信息
                        LlUserExt payUserExt = llUserExtService.getValidByUserId(payOrder.getUserId());
                        //TODO 查询收益用户连连钱包用户编号
                        LlUserExt userExt = llUserExtService.getValidByUserId(splitFlow.getUserId());

                        JSONObject result = llPayHelper.confirmSecuredConsume(userInfo, userExt, splitFlow, payOrder, payUserExt);
                        if (!StrUtil.equals(result.getString("ret_code"), SUCCESS_CODE)) {
                            splitFlow.setSplitStatus(SplitStatusEnum.FAIL);
                        }
                        splitFlow.setRespCode(result.getString("ret_code"));
                        splitFlow.setRespDesc(result.getString("ret_msg"));
                        splitFlow.setTransId(result.getString("accp_txno"));
                        splitFlow.setSplitType(SplitTypeEnum.SPLIT);
                        break;
                    default:
                        break;
                }
            }
            //只结算一次(状态变更，防止极限边界因素引起的重复结算,如三方返回处理中:那么最终结果可能会是成功也可能是失败) 更新结算状态
            if (splitFlow.getSplitStatus().equals(SplitStatusEnum.SUCCESS)) {
                //处理用户结算明细
                userOrderAccountFlowDao.settle(orderSettle.getUserId(), orderSettle.getPayChannel(),
                        orderSettle.getSettleAmount());
                //更新结算单状态
                orderSettleDao.settleOrder(orderSettle);
            } else {
                //更新为失败，避免持续堆积待结算订单
                orderSettle.setStatus(SettleAccountStatusEnum.FAILED);
                orderSettleDao.updateById(orderSettle);
            }
            //更新分账单为已完结
            splitFlow.setFinishTime(new Date());
            orderSplitFlowDao.updateById(splitFlow);

        } finally {
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 1000条用户数据500条平台数据 一次性总共结算1500条
     *
     * @param payChannelEnum
     * @return
     */
    @Override
    public List<OrderSettle> getUnSettle(PayChannelEnum payChannelEnum) {
        List<OrderSettle> orderSettles = new ArrayList<>();
        orderSettles.addAll(orderSettleDao.unSettleOrderForUser(payChannelEnum));
        orderSettles.addAll(orderSettleDao.unSettleOrderForPlat(payChannelEnum));
        return orderSettles;
    }

    @Override
    public IPage<OrderSettleDTO> getSettlePage(PayChannelEnum payChannelEnum, SettleAccountStatusEnum statusEnum,
                                               int pageNo, int pageSize, String startTime) {
        Long userId = UserPool.getUser().getId();
        return orderSettleDao.flowPage(userId, payChannelEnum, statusEnum, pageNo, pageSize, startTime);
    }

    @Override
    public void waitSettleOrder(TradingPoolOrder tradingPoolOrder) {
        tradingPoolOrder.setSettlementFlag(true);
        tradingPoolOrder.updateById();
        userWalletService.addIncomeRemit(tradingPoolOrder.getUserId(), tradingPoolOrder.getOrderNo(), tradingPoolOrder.getPrice(),
                tradingPoolOrder.getCharge(), tradingPoolOrder.getGas(), WalletDetailTypeEnum.TRANSACTION_POOL_SETTLE_AN_ACCOUNT,
                ExpenditureRecordTypeEnum.SELL);
    }

    @Override
    public void operationUserFailedSettleOrder(Long userId) {
        //查出所有失败的结算订单
        List<OrderSettle> orderSettles = orderSettleDao.getUserFailedSettleOrder(userId);
        BigDecimal unSettleAmount = BigDecimal.ZERO;
        for (OrderSettle orderSettle : orderSettles) {
            unSettleAmount = unSettleAmount.add(orderSettle.getSettleAmount());
        }
        //未结算金额大于0  且userAccount待结算金额大于等于未结算金额时将结算单更新成待结算 其他情况需要人工核实处理
        if (unSettleAmount.compareTo(BigDecimal.ZERO) > 0) {
            UserAccountFlow accountFlow = userOrderAccountFlowDao.queryOrInit(userId, PayChannelEnum.LL_PAY);
            if (accountFlow.getUnsettleAmount().compareTo(unSettleAmount) >= 0) {
                orderSettles.forEach(o -> o.setStatus(SettleAccountStatusEnum.UNSETTLE));
            }
        }
        //批量更新
        orderSettleDao.updateBatchById(orderSettles);

    }

}
